package com.ls.spring.framework.webmvc.servlet;

import com.ls.spring.framework.annotation.MyController;
import com.ls.spring.framework.annotation.MyRequestMapping;
import com.ls.spring.framework.context.MyApplicationContext;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 委派模式
 * 职责：负责任务调度，请求分发
 *
 * @author 挥之以墨
 */
public class MyDispatcherServlet extends HttpServlet {

    private static final long serialVersionUID = -710217827815923022L;

    private MyApplicationContext applicationContext;

    private List<MyHandlerMapping> handlerMappings = new ArrayList<>();

    private Map<MyHandlerMapping, MyHandlerAdapter> handlerAdapters = new HashMap<>();

    private List<MyViewResolver> viewResolvers = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 委派,根据URL去找到一个对应的Method并通过response返回
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            try {
                Map<String, Object> model = new HashMap<>(6);
                model.put("detail", e.getMessage());
                model.put("stackTrace", Arrays.toString(e.getStackTrace()));
                processDispatchResult(req, resp, new MyModelAndView("500", model));
            } catch (Exception e1) {
                e1.printStackTrace();
                resp.getWriter().write("500 Exception,Detail : " + Arrays.toString(e.getStackTrace()));
            }
        }
    }

    @Override
    public void init(ServletConfig config) {
        // 初始化IoC
        applicationContext = new MyApplicationContext(config.getInitParameter("contextConfigLocation"));

        // 初始化九大组件
        initStrategies();

        System.out.println("Spring framework is init.");
    }

    /**
     * 九大组件：
     * 1.多文件上传组件 -- MultipartResolver
     * 2.本地化语言环境 -- LocaleResolver
     * 3.模板处理器 -- ThemeResolver
     * 4.请求映射 -- HandlerMapping
     * 5.参数适配器 -- HandlerAdapter
     * 6.异常拦截器 -- HandlerExceptionResolver
     * 7.视图预处理器 -- RequestToViewNameTranslator
     * 8.视图转换器 -- viewResolver
     * 9.FlashMap管理器 -- FlashMap
     */
    private void initStrategies() {
        // 只初始化3个组件
        initHandlerMapping();
        initHandlerAdapter();
        initViewResolver();
    }

    private void initHandlerMapping() {
        if (this.applicationContext.getBeanDefinitionCount() == 0) {
            return;
        }

        for (String beanName : this.applicationContext.getBeanDefinitionNames()) {
            Object instance = this.applicationContext.getBean(beanName);
            Class<?> clazz = instance.getClass();
            // 验证是否有controller注解
            if (!clazz.isAnnotationPresent(MyController.class)) {
                continue;
            }
            // 获取类RequestMapping上的Url
            String baseUrl = "";
            if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
                baseUrl = clazz.getAnnotation(MyRequestMapping.class).value();
            }
            // 获取公用方法上的RequestMapping
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                    continue;
                }
                String methodUrl = method.getAnnotation(MyRequestMapping.class).value();
                String regex = ("/" + baseUrl + "/" + methodUrl.replaceAll("\\*", ".*")).replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(regex);
                handlerMappings.add(new MyHandlerMapping(pattern, method, instance));
                System.out.println("Mapped:" + regex + "," + method);
            }
        }
    }

    private void initHandlerAdapter() {
        for (MyHandlerMapping handlerMapping : this.handlerMappings) {
            this.handlerAdapters.put(handlerMapping, new MyHandlerAdapter());
        }
    }

    private void initViewResolver() {
        String templateRoot = this.applicationContext.getConfig().getProperty("templateRoot");
        URL url = this.getClass().getClassLoader().getResource(templateRoot);
        if (url == null) {
            return;
        }
        String filePath = url.getFile();
        this.viewResolvers.add(new MyViewResolver(new File(filePath)));
    }

    /**
     * 委派：请求分发并response返回
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        // 1、通过URL获得一个HandlerMapping
        MyHandlerMapping handler = getHandler(req);
        if (handler == null) {
            processDispatchResult(req, resp, new MyModelAndView("404"));
            return;
        }
        // 2、根据一个HandlerMapping获得一个HandlerAdapter
        MyHandlerAdapter handlerAdapter = getHandlerAdapter(handler);
        // 3、解析某一个方法的形参和返回值，并统一封装为ModelAndView对象
        MyModelAndView modelAndView = handlerAdapter.handler(req, resp, handler);
        // 4、把ModelAndView变成一个ViewResolver
        processDispatchResult(req, resp, modelAndView);
    }

    /**
     * 响应结果
     */
    private void processDispatchResult(HttpServletRequest req, HttpServletResponse resp, MyModelAndView mv) throws Exception {
        if (null == mv || this.viewResolvers.isEmpty()) {
            return;
        }
        MyView view = this.viewResolvers.get(0).resolveViewName(mv.getViewName());
        view.render(mv.getModel(), req, resp);
    }

    /**
     * 通过request请求中的url获取handlerMapping
     */
    private MyHandlerMapping getHandler(HttpServletRequest req) {
        if (this.handlerMappings.isEmpty()) {
            return null;
        }
        String uri = req.getRequestURI();
        String contextPath = req.getContextPath();
        uri = uri.replaceAll(contextPath, "").replaceAll("/+", "/");

        for (MyHandlerMapping handlerMapping : this.handlerMappings) {
            Matcher matcher = handlerMapping.getPattern().matcher(uri);
            if (matcher.matches()) {
                return handlerMapping;
            }
        }
        return null;
    }

    /**
     * 获取参数适配器
     */
    private MyHandlerAdapter getHandlerAdapter(MyHandlerMapping handler) {
        if (this.handlerAdapters.isEmpty()) {
            throw new RuntimeException("HandlerAdapter is not exists!");
        }
        return this.handlerAdapters.get(handler);
    }

}
